package com.freemypay.test.并发.共享受限资源.临界区;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * synchronized 方法和 synchronized 代码块(效率高)
 * 
 * @author yesixian
 * @date 2018年5月17日
 */
public class CriticalSection {
	// test the two different approaches:
	static void testApproaches(PairManager pman1, PairManager pman2) {
		ExecutorService exec = Executors.newCachedThreadPool();
		PairManipulator pm1 = new PairManipulator(pman1);
		PairManipulator pm2 = new PairManipulator(pman2);
		PairChecker pcheck1 = new PairChecker(pman1);
		PairChecker pcheck2 = new PairChecker(pman2);
		
		exec.execute(pm1);
		exec.execute(pm2);
		exec.execute(pcheck1);
		exec.execute(pcheck2);
		
		try {
			TimeUnit.MILLISECONDS.sleep(500);
		} catch (InterruptedException e) {
			System.out.println("sleep interrupted");
		}
		System.out.println("pm1: " + pm1 + "\npm2: " + pm2);
		System.exit(0);
	}
	
	public static void main(String[] args) {
		PairManager1 pman1 = new PairManager1();
		PairManager2 pman2 = new PairManager2();
		testApproaches(pman1, pman2);
	}
}

class Pair { // not thread-safe
	private int x,y;

	public Pair(int x, int y) {
		super();
		this.x = x;
		this.y = y;
	}

	public Pair() {
		this(0, 0);
	}

	public int getX() {
		return x;
	}

	public int getY() {
		return y;
	}
	
	public void incrementX() {
		x++;
	}
	
	public void incrementY() {
		y++;
	}

	@Override
	public String toString() {
		return "Pair [x=" + x + ", y=" + y + "]";
	}
	
	 public class PairValuesNotEqualException extends RuntimeException{
		 public PairValuesNotEqualException() {
			 super("pair values not equal: " + Pair.this);
		 }
	}
	 
	 // arbitrary invariant -- both variables must br equal
	 public void checkState() {
		 if(x != y) {
			 throw new PairValuesNotEqualException();
		 }
	 }
}

// protect a pair inside a thread-saft class
abstract class PairManager {
	AtomicInteger checkCounter = new AtomicInteger(0);
	protected Pair p = new Pair();
	private List<Pair> storage = Collections.synchronizedList(new ArrayList<Pair>());
	
	public synchronized Pair getPair() {
		// make a copy to keep the origianl safe
		return new Pair(p.getX(), p.getY());
	}
	
	// assume this is a time consuming operation
	protected void store(Pair p) {
		storage.add(p);
		try {
			TimeUnit.MILLISECONDS.sleep(50);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	public abstract void increment();
	
}

// synchronize the entire method:
class PairManager1 extends PairManager {

	@Override
	public synchronized void increment() {
		p.incrementX();
		p.incrementY();
		
		store(getPair());
	}
	
}

// use a critical section:
class PairManager2 extends PairManager {

	@Override
	public void increment() {
		Pair temp;
		synchronized (this) {
			p.incrementX();
			p.incrementY();
			temp = getPair();
		}
		store(temp);
	}
	
}

class PairManipulator implements Runnable {
	private PairManager pm;
	
	public PairManipulator(PairManager pm) {
		this.pm = pm;
	}

	@Override
	public void run() {
		while(true) {
			pm.increment();
		}
	}
	
	@Override
	public String toString() {
		return "pair: " + pm.getPair() + " checkCounter = " + pm.checkCounter.get();
	}
}

class PairChecker implements Runnable {
	private PairManager pm;
	
	public PairChecker(PairManager pm) {
		super();
		this.pm = pm;
	}

	@Override
	public void run() {
		while(true) {
			pm.checkCounter.incrementAndGet();
			pm.getPair().checkState();
		}
		
	}
	
}










